package com.wang.tool.event;

import com.wang.tool.common.ErrorInfo;
import com.wang.tool.monitor.InvokeTemplate;
import com.wang.tool.monitor.LinkLogUtil;
import com.wang.tool.monitor.core.MonitorInfo;
import com.wang.tool.monitor.template.MonitorInvoker;
import com.wang.tool.util.CommonUtil;
import com.wang.tool.util.SimpleLogUtil;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Executor;

/**
 * 事件总线
 *
 * @Author wangshaopeng
 * @Date 2022/6/11
 */
public class EventBus {


    public static final String TAG_EVENT_BUS_ASYNC_FIRE_ERROR = "EventBusAsyncFireError";

    /**
     * 监听表
     * eventType-id-listener
     */
    private final Map<Class<?>, Map<String, EventListener<Object>>> listeners = new HashMap<>();

    /**
     * 事件关联关系表
     */
    private final Map<Class<?>, Set<Class<?>>> realEventTypes = new HashMap<>();

    /**
     * 触发事件
     *
     * @param event
     * @return
     */
    public FireResult fire(Object event) {
        if (event == null) {
            throw new IllegalArgumentException("event can not be null");
        }
        Set<Class<?>> realTypes = getRealFireTypes(event.getClass());
        FireResult result = new FireResult();
        for (Class nowType : realTypes) {
            Map<String, EventListener<Object>> listeners = this.listeners.get(nowType);
            if (CommonUtil.isEmpty(listeners)) {
                LinkLogUtil.info("[fireEvent] the eventType:{0} has no listener", nowType.getName());
                continue;
            }
            for (EventListener<Object> listener : listeners.values()) {
                HandleResult handleResult = new HandleResult();
                handleResult.setId(listener.id());
                InvokeTemplate.monitor("EventBus", nowType.getSimpleName(), "fire", new MonitorInvoker<Object>(new Object[]{event}) {

                    @Override
                    public void weaveOnBefore(MonitorInfo<Object> monitorInfo) {
                        monitorInfo.addDigest("listenerId", listener.id());
                    }

                    @Override
                    public Object internalCall() throws Throwable {
                        Object obj = listener.onEvent(event);
                        handleResult.setResult(obj);
                        return obj;
                    }

                    @Override
                    public Object handleError(Throwable e, ErrorInfo error) {
                        handleResult.setError(error);
                        return null;
                    }

                    @Override
                    public void weaveOnAfter(MonitorInfo<Object> monitorInfo) {
                        monitorInfo.setStatus(String.valueOf(monitorInfo.getError() == null));
                    }
                });
                result.putResult(nowType, handleResult);
            }
        }
        return result;
    }

    /**
     * 异步触发
     *
     * @param executor 非空
     * @param event 事件 非空
     */
    public boolean asyncFire(Executor executor, Object event) {
        if (executor == null) {
            throw new IllegalArgumentException("executor can not be null");
        }
        try {
            executor.execute(() -> LinkLogUtil.info("[asyncFireEvent] event:{0}, result:{1}", event, fire(event)));
            return true;
        } catch (Throwable e) {
            LinkLogUtil.newTagLog(TAG_EVENT_BUS_ASYNC_FIRE_ERROR)
                    .addAttachment(MonitorInfo.ERROR, e)
                    .log();
            return false;
        }
    }


    /**
     * 注册
     *
     * @param listener 非空
     */
    public synchronized void register(EventListener<?> listener) {
        if (listener == null) {
            return;
        }
        String id = listener.id();
        if (id == null || listener.subscribeEventType() == null) {
            throw new IllegalArgumentException("id and careEvent can not be null");
        }

        Map<String, EventListener<Object>> listeners = this.listeners.computeIfAbsent(listener.subscribeEventType(), et -> new HashMap<>());
        if (listeners.containsKey(id)) {
            throw new IllegalArgumentException("the event:" + listener.subscribeEventType().getName() + " already has listener id:" + id + ", listener:" + listeners.get(id));
        }
        listeners.put(id, (EventListener<Object>) listener);
        SimpleLogUtil.info("[registerEventListener] event:{0}, listener:{1}", listener.subscribeEventType(), listener);
    }


    /**
     * 解注册
     *
     * @param listener
     */
    public synchronized void unRegister(EventListener<?> listener) {
        if (listener == null) {
            return;
        }
        Map<String, EventListener<Object>> listeners = this.listeners.get(listener.subscribeEventType());
        if (listeners != null && listeners.remove(listener.id()) != null) {
            SimpleLogUtil.info("[unRegisterEventListener] event:{0}, listener:{1}", listener.subscribeEventType(), listener);
        }
    }


    /**
     * 提取需要触发的真实类型事件
     *
     * @param rootType
     * @return
     */
    private Set<Class<?>> getRealFireTypes(Class<?> rootType) {
        return realEventTypes.computeIfAbsent(rootType, e -> {
            Set<Class<?>> types = new CopyOnWriteArraySet<>();
            types.add(e);
            recursionTypes(types, e);
            LinkLogUtil.info("[parseRealEventTypes] rootType:{0}, realTypes:{1}", e.getName(), types);
            return types;
        });
    }

    /**
     * 递归获取此类所有的爹
     *
     * @param types
     * @param type
     */
    private void recursionTypes(Set<Class<?>> types, Class<?> type) {
        Class<?> superclass = type.getSuperclass();
        if (superclass != null && !Object.class.equals(superclass)) {
            if (types.add(superclass)) {
                recursionTypes(types, superclass);
            }
        }

        Class<?>[] interfaces = type.getInterfaces();
        for (Class<?> oneIn : interfaces) {
            if (types.add(oneIn)) {
                recursionTypes(types, oneIn);
            }
        }
    }


    @Override
    public String toString() {
        return "EventBus{" +
                "listeners=" + listeners +
                ", realEventTypes=" + realEventTypes +
                '}';
    }
}
